﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;  

namespace GISCore.Geometry
{
    public enum Intersection { False, True, Collinear };
    [Serializable]
    public class Line : IEquatable<Line>
    {
        public Polygon polygon {get;set;} // Each line belongs to a polygon
        public Point Start { get; set; } // starting point
        public Point End { get; set; } //ending point
        //A, B, C are the coefficienct of the equation for the line
        public double A { get; set; }
        public double B { get; set; }
        public double C { get; set; }


        public Line parent { get; set; }
        public List<Line> children { get; set; }


        // The line in polygon has clockwise order.
        public Line next {get;set;}
        public Line prev { get; set;}
        // points that intersect with coordinator
        List<GridIntersectPoint> gridPoints;
        // points that intersec with other lines
        HashSet<IntersectPoint> intersectPoints;

        // points that intersec with other lines
        List<IntersectPoint> intersectionPoints;
        List<Point> points = new List<Point>();
        public int label { get; set; }
        public bool flag { get; set; }   
        
        //constructor
        public Line(Point start, Point end,bool test)
        {
            this.Start = start;
            
            this.End = end;
           
            this.Left = this.SweepPoint = (this.Start.X < this.End.X) ? this.Start : this.End;
            this.Right = this.Other = (this.Start.X > this.End.X) ? this.Start : this.End;
            this.Upper = (this.Start.Y > this.End.Y) ? this.Start : this.End;
            this.Lower = (this.Start.Y < this.End.Y) ? this.Start : this.End;

            //this.label = 1;
            flag = false;
            this.addPoint(start);
            this.addPoint(end);
            intersectPoints = new HashSet<IntersectPoint>();
            intersectionPoints = new List<IntersectPoint>();
            A = end.getY() - start.getY();

            //line is perpendicular to Y-axis
            if (A == 0)
            {
                B = 1;
                C = -end.getY();
            }
            else
            {
                B = start.getX() - end.getX();
                if (B == 0)
                {
                    A = 1;
                    C = -end.getX();
                }
                else
                {
                    C = -(A * start.getX()) - B * start.getY();
                }
            }

        }
        public Line(Point start, Point end) 
        {
            this.Start = start;
            //start.setLine(this);
            this.End = end;
            //end.setLine(this);
            this.Left = this.SweepPoint = (this.Start.X < this.End.X) ? this.Start : this.End;
            this.Right = this.Other = (this.Start.X > this.End.X) ? this.Start : this.End;
            this.Upper = (this.Start.Y > this.End.Y) ? this.Start : this.End;
            this.Lower = (this.Start.Y < this.End.Y) ? this.Start : this.End;

            //this.label = 1; 
            flag = false;
            this.addPoint(start);
            this.addPoint(end);
            intersectPoints = new HashSet<IntersectPoint>();
            intersectionPoints = new List<IntersectPoint>();
            A = end.getY() - start.getY();

            //line is perpendicular to Y-axis
            if (A == 0)
            {
                B = 1;
                C = -end.getY();
            }
            else
            {
                B = start.getX() - end.getX();
                if (B == 0)
                {
                    A = 1;
                    C = -end.getX();
                }
                else
                {
                    C = -(A * start.getX()) - B * start.getY();
                }
            }

            
        }

        public static Line generateDummyLine()
        {
            return new Line(new Point(1, 1), new Point(1, 1));
        }

        private static void swap(ref double a, ref double b)
        {
            double temp = a;
            a = b;
            b = temp;
        }

        private static void swapPoints(ref Point a, ref Point b)
        {
            Point temp = a;
            a = b;
            b = temp;
        }

        public override string ToString()
        {
            return string.Format("{0} -> {1}", this.Start, this.End);
        }


        public bool AddIntersection(IntersectPoint p)
        {
            bool val = false;
            if ((this.Left != p && this.Right != p) &&
                (this.intersectPoints.Add(p)))
            {
                this.intersectionPoints.Add(p);
                this.intersectionPoints.Sort();
                val = true;
            }
            return val;
        }

        /* Add and sort the points*/
        public void addIntersectPoint(IntersectPoint p)
        {
            this.points.Add(p);
            this.SortPoints();
        }

        public Point Right
        {
            get;
            private set;
        }

        public Point Left
        {
            get; 
            private set;
        }

        public Point Upper
        {
            get;
            private set;
        }

        public Point Lower
        {
            get;
            private set;
        }

        public Point SweepPoint { get; set; }
        public Point Other { get; set; }

        public bool ContainsIntersection(Point p)
        {
            return this.intersectPoints.Contains(p);
        }

        public double getSweepPoint(double y)
        {
            if (A == 0)
            {
                if (Start.getX() > End.getX())
                {
                    return End.getX();
                }
                else
                {
                    return Start.getX();
                }
            } 
            return (B * y + C) / (-A);
        }
        public Intersection getIntersect(Line line, out IntersectPoint result)
        {
            double A1 = this.getACoefficient();
            double B1 = this.getBCoefficient();
            double C1 = this.getCCoefficient();

            double A2 = line.getACoefficient();
            double B2 = line.getBCoefficient();
            double C2 = line.getCCoefficient();

            result = default(IntersectPoint);

            //either lines are collinear or parallel
            if (Math.Abs(A1 * B2 - A2 * B1) <=Constants.ZERO) 
            {
                // Lines are collinear
                if (Math.Abs(B1 * C2 - B2 * C1) <= Constants.ZERO)
                {
                    return Intersection.Collinear;
                }
                // lines are parallel
                else
                {
                    return Intersection.False;
                }
            }

            double x = (B1 * C2 - B2 * C1) / (A1 * B2 - A2 * B1);

            double y = (A2 * C1 - A1 * C2) / (A1 * B2 - A2 * B1);

            // check for intersection, lines may intersect on reproducing
            // for intersection detection, let line.start.x < line.end.x , if it
            // is not the case then swap start.x and end.x
            double startXCord1 = this.Start.getX();
            double endXCord1 = this.End.getX();

            //swap start x and end x coordinate

            if (startXCord1 > endXCord1)
            {
                swap(ref startXCord1, ref endXCord1);
            }

            double startYCord1 = this.Start.getY();
            double endYCord1 = this.End.getY();

            if (startYCord1 > endYCord1)
            {
                swap(ref startYCord1, ref endYCord1);
            }

            double startXCord2 = line.Start.getX();
            double endXCord2 = line.End.getX();

            //swap start x and end x coordinate 

            if (startXCord2 > endXCord2)
            {
                swap(ref startXCord2, ref endXCord2);
            }

            // similarly for y coordinate
            double startYCord2 = line.Start.getY();
            double endYCord2 = line.End.getY();

            if (startYCord2 > endYCord2)
            {
                swap(ref startYCord2, ref endYCord2);
            }

            if ((x >= startXCord2 && x <= endXCord2) &&
                (y >= startYCord2 && y <= endYCord2) &&
                (x >= startXCord1 && x <= endXCord1) &&
                (y >= startYCord1 && y <= endYCord1))
            {
                result = new IntersectPoint(x, y);

                //Add the new intersection points in both the lines
                //this.points.Add(result);
                //line.points.Add(result);
                //sorting all the points in the line
                //this.points.Sort();
                //line.points.Sort();
                return Intersection.True;
            }
            else
            {
                return Intersection.False;
            }
        }

        /* Adds intersection point to the intersecting lines and sort the points in the lines*/
        public Intersection addIntersectPoint(Line line, out IntersectPoint result)
        {
            double A1 = this.getACoefficient();
            double B1 = this.getBCoefficient();
            double C1 = this.getCCoefficient();

            double A2 = line.getACoefficient();
            double B2 = line.getBCoefficient();
            double C2 = line.getCCoefficient();

            result = default(IntersectPoint);

            //either lines are collinear or parallel
            if (A1 * B2 - A2 * B1 == 0.0)
            {
                // Lines are collinear
                if (B1 * C2 - B2 * C1 == 0.0)
                {
                    return Intersection.Collinear;
                }
                // lines are parallel
                else
                {
                    return Intersection.False;
                }
            }

            double x = (B1 * C2 - B2 * C1) / (A1 * B2 - A2 * B1);

            double y = (A2 * C1 - A1 * C2) / (A1 * B2 - A2 * B1);

            // check for intersection, lines may intersect on reproducing
            // for intersection detection, let line.start.x < line.end.x , if it
            // is not the case then swap start.x and end.x
            double startXCord1 = this.Start.getX();
            double endXCord1 = this.End.getX();

            //swap start x and end x coordinate

            if (startXCord1 > endXCord1)
            {
                swap(ref startXCord1, ref endXCord1);
            }

            double startYCord1 = this.Start.getY();
            double endYCord1 = this.End.getY();

            if (startYCord1 > endYCord1)
            {
                swap(ref startYCord1, ref endYCord1);
            }

            double startXCord2 = line.Start.getX();
            double endXCord2 = line.End.getX();

            //swap start x and end x coordinate

            if (startXCord2 > endXCord2)
            {
                swap(ref startXCord2, ref endXCord2);
            }

            // similarly for y coordinate
            double startYCord2 = line.Start.getY();
            double endYCord2 = line.End.getY();

            if (startYCord2 > endYCord2)
            {
                swap(ref startYCord2, ref endYCord2);
            }

            if ((x >= startXCord2 && x <= endXCord2) &&
                (y >= startYCord2 && y <= endYCord2) &&
                (x >= startXCord1 && x <= endXCord1) &&
                (y >= startYCord1 && y <= endYCord1))
            {
                result = new IntersectPoint(x, y);

                /*Add the new intersection points in both the lines*/
                this.points.Add(result);
                line.points.Add(result);
                /* sorting all the points in both lines */
                this.points.Sort();
                line.points.Sort();
                return Intersection.True;
            }
            else
            {
                return Intersection.False;
            }
        }

        public void ensureClockWiseOrder(List<Point> points)
        {
            if ((this.Start.X != points[0].X) && (this.Start.Y != points[0].Y ))
            {
                points.Reverse();
            }
        }

        public void AddPoints(IEnumerable<GridIntersectPoint>  gridPtList)
        {
            this.points.AddRange(gridPtList);
        }

        public Line getPrev()
        {
            return prev;
        }

        public void setPrev(Line prev)
        {
            this.prev = prev;
        }

        public Line getNext()
        {
            return next;
        }

        public void setNext(Line next)
        {
            this.next = next;
        }

        public Polygon getPolygon()
        {
            return this.polygon;
        }
        public void setPolygon(Polygon poly)
        {
            this.polygon = poly;
        }

        public double getACoefficient()
        {
            return A;
        }
        public double getBCoefficient()
        {
            return B;
        }
        public double getCCoefficient()
        {
            return C;
        }

        public void addPoint(Point l)
        {
            this.points.Add(l);
        }

        public void setPoints(List<Point> pointList)
        {
            this.points = pointList;
        }
        public void clearPoint()
        {
            this.points.Clear();
        }

        public List<Point> getPoints()
        {
            return this.points;
        }

        public void SortPoints()
        {
            points.Sort();
            ensureClockWiseOrder(this.points);
        }

        public override bool Equals(object obj)
        {
            return (obj is Line) ? this.Equals(obj as Line)
                : false;
        }

        public bool Equals(Line other)
        {
            return other != null && this.Left == other.Left &&
                this.Right == other.Right;
        }
       
    }
}
